/**
 * @class 银河系对象实体
 * @author DuskyHuang 圣子
 * @description 负责银河系的渲染和逻辑。
*/

import { _decorator, Component, instantiate, Node, Prefab, Vec3, Material, renderer, MeshRenderer, Vec4, Color, Camera } from 'cc';
import { core } from 'cfw';
import {
    AxisRotation, Res,
    //  RendererType, 
    MILKY_TO_SOLAR, Diameters
} from 'const';
import { cosmos, CosmosProxy, DeepGalaxiesProxy } from 'proxies';
import { setClouds, setColorBySpectrum, setLabelStyle, setParticles } from 'helpers';
// import { Renderer } from 'notifications';
import { CelestialLabel } from '../ui/items/celestial-label';
import { Task, TaskNames } from 'notifications';
const { ccclass, property } = _decorator;

@ccclass('cocosmos.MilkyWay')
export class MilkyWay extends Component {

    @property(Prefab) public MilkyDisk: Prefab;

    public get structureLabels(): CelestialLabel[] { return this._labels; }

    public claimStructureLabelsIfNotExist(viewer: Camera): void {
        if (notEmptyArr(this._labels)) return;
        Structures.forEach(structure => {
            const [cn, en, ofs, main] = structure as structure;
            const label = CelestialLabel.claim('milky', 'milky-structure');
            label.target = this.node;
            label.camera = viewer;
            setLabelStyle(label, 'milky', main);
            label.setOffset(ofs as Vec3);
            // label.string = cn;
            label.config = { en, cn };
            this._labels.push(label);
        });
    }

    public freeStructureLabels(): void {
        if (notEmptyArr(this._labels)) this._labels.forEach(label => label.free());
        this._labels.length = 0;
    }

    public update(dt?: time.sec): void {
        const { pow10 } = cosmos;
        this._updateClouds(pow10);
        this._updateStars(pow10);
        this._updateDisks(pow10);
    }

    public lateUpdate(): void {
        if (!MilkyWay.LoadTexTaskLaunched && (cosmos.pow10 - 0.5) >= CosmosProxy.Scopes.MilkyWay[0]) {
            MilkyWay.LoadTexTaskLaunched = true;
            core.notify(Task.Load.DYNAMIC_TEX, TaskNames.TEX_MILKY_HOR);
            core.notify(Task.Load.DYNAMIC_TEX, TaskNames.TEX_MILKY_VER);
        }
    }

    protected onLoad(): void { this._init()._listen(); }

    private _init(): this {
        this._disks = this.node.getChildByName('Disks');
        this._clouds = this.node.getChildByName('Clouds');
        this._stars = this.node.getChildByName('Stars');
        this._core = this.node.getChildByName('Core');
        [this._disks, this._clouds, this._core].forEach(child => {
            child.setPosition(MILKY_TO_SOLAR);
            if (child != this._core) child.setRotationFromEuler(GALACTIC);
        });
        return this;
    }

    private _updateStars(pow10: pow10): void {
        const [FROM, TO] = STARS_RANGE;
        if (pow10 > FROM && pow10 < TO) {
            const s = Math.clamp(Math.pow(3, pow10 - 4), 0, 1);
            this._stars.setScale(s, s, s);
            if (this._stars.children.length <= 0) {
                this._claimStars(this.node.scale.x < 1 ? 2 : 0.01);
            }
            this._stars.active = this._core.active = true;
        } else {
            // this._freeStars();
            this._stars.active = this._core.active = false;
        }
    }

    private _claimStars(ratio: num.ratio): void {
        const args: IParticleLike = {
            num: PARTICLE_NUM,
            sizes: [MIN * ratio, MAX * ratio],
            radius: MILKY * 0.6,
            thickness: 0.85,
            position: MILKY_TO_SOLAR,
            euler: GALACTIC,
            scale: v3a.set(Vec3.ONE)
        };
        // core.notify(Renderer.CLAIM, RendererType.STARS_GPU, this._stars, args);
        setParticles(instantiate(core.res.getPrefab(STARS_GPU)), args, this._stars);
        v3a.set(1, THICKNESS, 1);
        ColorGradients.forEach(([from, to]) => {
            const c1 = new Color, c2 = new Color;
            setColorBySpectrum(c1, from);
            setColorBySpectrum(c2, to);
            args.colors = [c1, c2];
            // core.notify(Renderer.CLAIM, RendererType.STARS_GPU, this._stars, args);
            setParticles(instantiate(core.res.getPrefab(STARS_GPU)), args, this._stars);
        });
    }

    // private _freeStars(): void {
    //     this._stars.children.forEach(child =>
    //         core.notify(Renderer.FREE, RendererType.STARS_GPU, child));
    // }

    /**
     * @deprecated 
     * @description 原先使用了四张4K高清图片拼接成超高清大图，
     * 但银盘在拉进视野时本身就会淡化，提升效果并不明显所以弃用。
     */
    private async _createHighSolutionDisks(): Promise<void> {
        const HRadius = MILKY / 4;
        for (const idx of Array.nature(3, 0).reverse()) {
            const disk = instantiate(this.MilkyDisk);
            disk.setPosition(idx % 2 == 0 ? -HRadius : HRadius, 0, idx < 2 ? HRadius : -HRadius);
            disk.parent = this._disks;
            const mat = disk.getComponent(MeshRenderer)!.material;
            this._cMats.push(mat);
            this._cHandles.push(mat.passes[0].getHandle('attenuation'));
            mat.setProperty('mainTexture', await core.res.loadRemoteTexture(`${MILKY_WAY}/disk-${idx}`, 'jpg'));
        }
    }

    private async _createDisks(): Promise<void> {
        if (true) {
            const disk = instantiate(this.MilkyDisk);
            disk.parent = this._disks;
            const mat = disk.getComponent(MeshRenderer)!.material;
            this._cMats.push(mat);
            this._cHandles.push(mat.passes[0].getHandle('attenuation'));
            mat.setProperty('mainTexture', await core.res.loadRemoteTexture(`${MILKY_WAY}/disk-horizontal`, 'jpg'));
        } else this._createHighSolutionDisks();
        for (const idx of Array.nature(2).reverse()) {
            const vert = instantiate(this.MilkyDisk);
            vert.setPosition(0, 0.05, 0);
            vert.setScale(10, 2, 1);
            vert.setRotationFromEuler(0, idx * 60, 0);
            vert.parent = this._disks;
            const mat = vert.getComponent(MeshRenderer)!.material;
            this._cMats.push(mat);
            this._cHandles.push(mat.passes[0].getHandle('attenuation'));
            mat.setProperty('mainTexture', await core.res.loadRemoteTexture(`${MILKY_WAY}/disk-vertical`, 'jpg'));
        };
    }

    private _updateDisks(pow10: pow10): void {
        if (pow10 > DISKS_RANGE.head) {
            this._disks.active = true;
            if (this._disks.children.length <= 0) this._createDisks();
            const near = Math.pow(10, 2.5 - pow10);
            v4a.set(near, near * 5, 10, 20);
            this._cMats.forEach((mat, idx) => {
                mat.passes[0].setUniform(this._cHandles[idx], v4a);
            });
        } else this._disks.active = false;
    }

    private _updateClouds(pow10: pow10): void {
        const [FROM, TO] = CLOUDS_RANGE;
        if (pow10 > FROM && pow10 < TO) {
            if (this._clouds.children.length <= 0) this._claimClouds();
            this._clouds.active = true;
        } else this._clouds.active = false;
        // this._freeClouds();
    }

    private _claimClouds(): void {
        const farArgs: ICloudLike = { euler: v3a.set(Vec3.ZERO), scale: v3b.set(Vec3.ONE) };
        const STEP = MILKY / LAYERS;
        for (let i = 1; i <= LAYERS; i++) {
            const s = STEP * i;
            farArgs.scale.x = farArgs.scale.z = s; farArgs.scale.y = s * HIGHT;
            farArgs.euler.y = Math.randomInt(0, 360);
            // core.notify(Renderer.CLAIM, RendererType.CLOUDS_INST, this._clouds, args);
            setClouds(instantiate(core.res.getPrefab(MILKY_CLOUD)), farArgs, this._clouds);
        }
        const nearArgs: ICloudLike = { worldPosition: v3a.set(Vec3.ZERO), euler: v3b.set(Vec3.ZERO), scale: v3c.set(Vec3.ONE) };
        for (let i = 1; i <= LAYERS; i++) {
            const s = STEP * i / 5;
            nearArgs.scale.x = nearArgs.scale.z = s; nearArgs.scale.y = s * HIGHT * 2;
            nearArgs.euler.y = Math.randomInt(0, 360);
            // core.notify(Renderer.CLAIM, RendererType.CLOUDS_INST, this._clouds, argsSolar);
            setClouds(instantiate(core.res.getPrefab(MILKY_CLOUD)), nearArgs, this._clouds);
        }
    }

    // private _freeClouds(): void {
    //     this._clouds.children.forEach(child =>
    //         core.notify(Renderer.FREE, RendererType.CLOUDS_INST, child));
    // }

    private _listen(): this { return this; }

    private _cMats: (Material | renderer.MaterialInstance)[] = [];
    private _cHandles: number[] = [];
    private _disks: Node;
    private _stars: Node;
    private _clouds: Node;
    private _core: Node;
    private _labels: CelestialLabel[] = [];
    private static LoadTexTaskLaunched: boolean = false;
}

const { MILKY_WAY } = Res.Textures;
const { MILKY } = Diameters;
const { GALACTIC } = AxisRotation;
const { THICKNESS, GPUStars: { SizesRange: [MIN, MAX], PARTICLE_NUM, ColorGradients },
    CLouds: { HIGHT, LAYERS }, Structures } = DeepGalaxiesProxy.MilkyWay;
const { Prefabs: { STARS_GPU, MILKY_CLOUD } } = Res;
const DISKS_RANGE = [2.5, Number.MAX_VALUE];
const STARS_RANGE = [2, 5.3];
const CLOUDS_RANGE = [1.5, 5.8];

const v3a = new Vec3;
const v3b = new Vec3;
const v3c = new Vec3;
const v4a = new Vec4;